/**
 * Global namespace for all maps related classes and functions.
 */
var Maps = {
  
  HTTPRequestMethods : [
    function() {return new XMLHttpRequest();},
    function() {return new ActiveXObject("Msxml3.XMLHTTP");},
    function() {return new ActiveXObject("Msxml2.XMLHTTP");},
    function() {return new ActiveXObject("Microsoft.XMLHTTP");},
    function() {return null;}
  ],
  
  urlPathPrefix : "/js/phpmapsapi",
  
  /**
   * Gets a browser compatible AJAX object.
   * @param   void
   * @return  object  XMLHttpRequest object (or ActiveX equivalent)
   */
  getHTTPRequest : function () {
    var i = 0;
    var end = this.HTTPRequestMethods.length;
    var req;
    
    while (i < end) {
      try {
        req = this.HTTPRequestMethods[i]();
        this.getHTTPRequest = this.HTTPRequestMethods[i];
        this.HTTPRequestMethods = null;
        break;
      } catch (e) {
        continue;
      }
      i++;
    }
    
    return req;
  },
  
  /**
   * Loads a PHP Maps API JavaScript class
   * @param   string  Class name to load
   * @return  boolean True if loaded, false otherwise
   */
  load : function (className) {
    var async = false;
    var method = "get";
    var req = this.getHTTPRequest();
    var success = false;
    var url = this.urlPathPrefix + "/" + className + ".js";
    
    if (!req) {
      return false; // Incompatible browser
    }
    
    req.open(method, url, async);
    req.send(null);
    
    if (req.status == 200) {
      try {
        eval(req.responseText);
        success = true;
      } catch (e) {
        success = false;
      }
    } else {
      success = false;
    }
    
    return success;
  },
  
  /**
   * Called window.onload event handler to initialize all the maps on the page.
   * @param   void
   * @return  void
   */
  onload : function () {
    Maps.Icons.Manager.Factory.onload();
    Maps.Manager.Factory.onload();
    Maps.loaded = true;
  },
  
  /**
   * Called window.onunload event handler to clean up all the maps on the page.
   * @param   void
   * @return  void
   */
  onunload : function () {
    Maps.Icons.Manager.Factory.destroy();
    Maps.Manager.Factory.destroy();
  },
  
  /**
   * Capitalizes the first letter in a string.
   * @param   string  String whose first letter should be capitalized
   * @return  string  String with first letter capitalized
   */
  ucfirst : function (str) {
    return (str.charAt(0)).toUpperCase() + str.substring(1, str.length);
  }
  
};

/**
 * Namespace for icon related classes
 */
Maps.Icons = {};

/**
 * Manages a group of map service specific icons
 * @param   void
 * @return  void
 */
Maps.Icons.Manager = function() {
  
  /**
   * Icons stored as name -> icon
   * @access  private
   * @type    object
   */
  var icons = {};
  
  /**
   * Number of icons stored
   * @access  private
   * @type    integer
   */
  var count = 0;
  
  /**
   * Adds an icon to this manager
   * @access  public
   * @param   string  Name of this icon
   * @param   object  Map service icon object
   * @return  string  Name used to retrieve this icon object later.
   */
  this.add = function(name, icon) {
    if ( !icons[name] ) {
      icons[name] = icon;
      count++;
    }
    icon = null;
    return name;
  };
  
  /**
   * Gets an icon object.
   * @access  public
   * @param   string  Name of this icon
   * @return  object  Icon object, or null if name isn't found.
   */
  this.get = function(name) {
    return icons[name] ? icons[name] : null;
  };
  
  /**
   * Class destructor to prevent memory leaks
   * @access  public
   * @param   void
   * @return  void
   */
  this.destroy = function() {
    var key = "";
    
    for (key in icons) {
      delete icons[key];
    }
    count = 0;
  };
  
  /**
   * Function called in onload event.
   * @access  public
   * @param   void
   * @return  void
   */
  this.load = function() {};
  
};

/**
 * Static class to create and manage icon manager objects.
 */
Maps.Icons.Manager.Factory = new (function() {
  
  /**
   * Icon manager objects as service -> icon manager
   * @access  private
   * @type    object
   */
  var managers = {};
  
  /**
   * Creates an icon manager object for a mapping service.
   * @access  public
   * @param   string  Map service name, e.g. "google", "yahoo", etc.
   * @param   func    Function to be called when page loads.
   * @return  void
   */
  this.create = function(service, load) {
    if (managers[service]) {
      return;
    }
    managers[service] = new Maps.Icons.Manager();
    managers[service].load = load || function() {};
  };
  
  /**
   * Gets an icon manager object for a mapping service.
   * @access  public
   * @param   string  Map service name
   * @return  object  Icon manager object, or null if service not found.
   */
  this.get = function(service) {
    if ( !managers[service] ) {
      this.create(service);
    }
    return managers[service] ? managers[service] : null;
  };
  
  /**
   * Class destructor to prevent memory leaks.
   * @access  public
   * @param   void
   * @return  void
   */
  this.destroy = function() {
    var service = "";
    
    for (service in managers) {
      if (managers[service].destroy) {
        managers[service].destroy();
      }
    }
    managers = null;
  };
  
  /**
   * Calls the load() function on all icon managers.
   * @access  public
   * @param   void
   * @return  void
   */
  this.onload = function() {
    var service = "";
    
    for (service in managers) {
      if (managers[service].load) {
        managers[service].load();
      }
    }
  };
  
})();

/**
 * Namespace for map manager related classes
 */
Maps.Manager = {};

/**
 * Base class for all map manager objects.
 * @param   void
 * @return  void
 */
Maps.Manager.Common = function() {
  if (Maps.Manager.Common.extending) {
    return;
  }
  
  this.fns = [];
  this.maps = [];
}

Maps.Manager.Common.prototype = {
  
  /**
   * Functions executed onload
   * @access  public
   * @type    array
   */
  fns : null,
  
  /**
   * Map service specific objects
   * @access  public
   * @type    array
   */
  maps : null,
  
  /**
   * Whether or not this browser is supported
   * @access  public
   * @type    boolean
   */
  supported : null,
  
  /**
   * Adds a function to this manager, which creates a map object.
   * @access  public
   * @param   func    Function to be executed onload
   * @return  int     Index at which this function was executed.
   */
  add : function(fn) {
    return this.fns.push(fn) - 1;
  },
  
  /**
   * Class destructor to prevent memory leaks.
   * @access  public
   * @param   void
   * @return  void
   */
  destroy : function() {
    var i = this.maps.length;
    
    while (i--) {
      this.destroySidebar( this.getMapId(this.maps[i]) + "_sidebar" );
    }
    
    this.maps = null;
  },
  
  /**
   * Destroy a sidebar to prevent memory leaks.
   * @access  public
   * @param   string  HTML tag Id to a map side bar.
   * @return  void
   */
  destroySidebar : function(id) {
    var sidebar = document.getElementById(id);
    var sidebarLinks = null;
    var i = 0;
    
    if (!sidebar) {
      return;
    }
    
    sidebarLinks = sidebar.getElementsByTagName("a");
    i = sidebarLinks.length;
    
    while (i--) {
      sidebarLinks[i].onclick = null;
    }
    
    sidebar = null;
    sidebarLinks = null;
  },
  
  /**
   * Displays all maps added to this manager.
   * @access  public
   * @param   void
   * @return  void
   */
  displayAll : function() {
    if ( !this.isSupported() ) {
      return;
    }
    
    var i = 0;
    var end = this.fns.length;
    
    while (i < end) {
      this.maps.push( this.fns[i].call(this) );
      i++;
    }
  },
  
  /**
   * Defined in implementing classes
   * @access  public
   * @param   string  HTML tag id of map
   * @return  null
   */
  getById : function(id) {return null;}
  
};

Maps.Manager.Common.extending = false;

/**
 * Factory class to create map manager objects.
 */
Maps.Manager.Factory = {
  
  /**
   * Map manager objects as service -> manager object
   * @access  public
   * @type    object
   */
  managers : {},
  
  /**
   * Creates a map manager object for a mapping service.
   * @access  public
   * @param   string  Map service name, e.g. "google"
   * @return  object  Map manager object or null if not supported
   */
  create : function(service) {
    var serviceUC = Maps.ucfirst(service);
    var className = "Maps.Manager." + serviceUC;
    
    if ( Maps.Manager[serviceUC] || Maps.load(className) ) {
      return new Maps.Manager[serviceUC]();
    } else {
      return null;
    }
  },
  
  /**
   * Class destructor to prevent memory leaks. Automatically calls destructor methods on map managers.
   * @access  public
   * @param   void
   * @return  void
   */
  destroy : function() {
    var service = "";
    
    for (service in this.managers) {
      if ( typeof(this.managers[service].destroy) == "function" ) {
        this.managers[service].destroy();
      }
    }
  },
  
  /**
   * Gets a map manager object. Attempts to create a map manager object when one for that service is not found.
   * @access  public
   * @param   string  Map service type.
   * @return  object  Map manager object or null if service not supported
   */
  get : function(service) {
    if ( typeof(this.managers[service]) != "object" ) {
      this.managers[service] = this.create(service);
    }
    return this.managers[service];
  },
  
  /**
   * Calls displayAll() function on all map managers
   * @access  public
   * @param   void
   * @return  void
   */
  onload : function() {
    var service = "";
    
    for (service in this.managers) {
      this.managers[service].displayAll();
    }
  }

};